package middleware

import (
	"errors"
	"fmt"
	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
	"github.com/jhoncy/schelper/model"
	"net/http"
	"reflect"
	"strings"
)

type JwtConfig struct {
	GetKey        string
	AuthScheme    string
	SigningKey    interface{}
	SigningMethod string
	TokenLookup   string
	Claims        jwt.Claims
	keyFunc       jwt.Keyfunc
	ErrorHandler  JWTErrorHandler
}

type jwtExtractor func(*gin.Context) (string, error)

type JWTErrorHandler func(error) error

const (
	AlgorithmHS256 = "HS256"
)

var DefaultJwtConfig = JwtConfig{
	GetKey:        "auth",
	SigningMethod: AlgorithmHS256,
	AuthScheme:    "Bearer",
	TokenLookup:   "header:Authorization",
	Claims:        jwt.MapClaims{},
}


func JwtAuth() gin.HandlerFunc{
	return jwtAuthWithConfig(JwtConfig{SigningKey: []byte(model.Config().Jwt.Signed)})
}


func jwtAuthWithConfig(config JwtConfig) gin.HandlerFunc {
	if config.SigningKey == nil {
		panic("yee: jwt middleware requires signing key")
	}
	if config.SigningMethod == "" {
		config.SigningMethod = DefaultJwtConfig.SigningMethod
	}
	if config.GetKey == "" {
		config.GetKey = DefaultJwtConfig.GetKey
	}
	if config.AuthScheme == "" {
		config.AuthScheme = DefaultJwtConfig.AuthScheme
	}

	if config.Claims == nil {
		config.Claims = DefaultJwtConfig.Claims
	}

	if config.TokenLookup == "" {
		config.TokenLookup = DefaultJwtConfig.TokenLookup
	}
	config.keyFunc = func(token *jwt.Token) (interface{}, error) {
		if token.Method.Alg() != config.SigningMethod {
			return nil, fmt.Errorf("unexpected jwt signing method=%v", token.Header["alg"])
		}
		return config.SigningKey, nil
	}
	parts := strings.Split(config.TokenLookup, ":")
	extractor := jwtFromHeader(parts[1], config.AuthScheme)
	return func(c *gin.Context) {
		auth, err := extractor(c)
		if err !=nil {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
				"error": err.Error(),
			})
			return
		}
		token := new(jwt.Token)
		if _, ok := config.Claims.(jwt.MapClaims); ok {
			token, err = jwt.Parse(auth, config.keyFunc)
			if err != nil {
				c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
					"error": err.Error(),
				})
				return
			}
		} else {
			t := reflect.ValueOf(config.Claims).Type().Elem()
			claims := reflect.New(t).Interface().(jwt.Claims)
			token, err = jwt.ParseWithClaims(auth, claims, config.keyFunc)
		}

		if err != nil {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
				"error": err.Error(),
			})
			return
		}
		if !token.Valid {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
				"error": "token invalid",
			})
			return
		}
		c.Set(config.GetKey, token)
		c.Next()
	}
}

func jwtFromHeader(header string, authScheme string) jwtExtractor {
	return func(c *gin.Context) (string, error) {
		auth := c.GetHeader(header)
		l := len(authScheme)
		if len(auth) > l+1 && auth[:l] == authScheme {
			return auth[l+1:], nil
		}
		return "", errors.New("missing or malformed jwt")
	}
}

